﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/31 16:27:44 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Common.ORM;
using Machine.DataAccess.Linq;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Machine.DataAccess.Operation
{
    abstract class DataSchemCreatorBase : IDataSchemCreator
    {
        public ProviderElement ProviderElement { get; private set; }
        protected abstract string SQL_CreateTable { get; }
        protected abstract string SQL_CreateBasic { get; }
        protected abstract string SQL_CreateRelation { get; }

        protected static readonly string COLUMARG_NAME = "@CloumName";
        protected static readonly string TABLEARG_NAME = "@TableName";
        protected static readonly string COLUMTYPEARG_NAME = "@ColumType";
        protected static readonly string OUTTERTABLE_NAME = "@OutterTable";
        protected static readonly string OUTTERKEY_NAME = "@OutterKey";
        private static object _sync = new object();

        public DataSchemCreatorBase(ProviderElement providerElement)
        {
            this.ProviderElement = providerElement;
        }

        public abstract bool CreateTable(string tableName, string primaryKeyName, string primaryKeyType,bool isAutoKey = false);
        //{
        //    var strTemp = SQL_CreateTable.Replace(TABLEARG_NAME, tableName)
        //        .Replace(COLUMARG_NAME, primaryKeyName)
        //        .Replace(COLUMTYPEARG_NAME, primaryKeyType);
        //    return DataResult.ExecuteNonQuery(new TranslateResult(strTemp, new DbParameter[0]));
        //}

        public abstract bool CreateBaseType(Type table, PropertyInfo colum);
        //{
        //    //var pk = colum;
        //    //List<DbParameter> list = new List<DbParameter>();
        //    //list.Add(this.CreateParameter(TABLEARG_NAME, table.GetTableName()));
        //    //list.Add(this.CreateParameter(COLUMARG_NAME, pk.Name));
        //    //list.Add(this.CreateParameter(COLUMTYPEARG_NAME, pk.PropertyType));
        //    var strTemp = SQL_CreateBasic.Replace(TABLEARG_NAME, table.GetTableName())
        //        .Replace(COLUMARG_NAME, colum.Name)
        //        .Replace(COLUMTYPEARG_NAME, colum.PropertyType.Name);

        //    //return DataResult.ExecuteNonQuery(new TranslateResult(SQL_CreateBasic, list));
        //    return DataResult.ExecuteNonQuery(new TranslateResult(strTemp, new DbParameter[0]));
        //}
        public abstract bool CreateFK(string tableName, string columName, string columType, string outterTable, string outterKey);

        //public abstract bool CreateRelation(string firstTableName, string firstPropertyName, string firstPropertyType,
        //    string secondTableName, string secondPropertyName, string secondPropertyType);

        protected DbParameter CreateParameter(string parameterName, object value)
        {
            var parameter = DbProviderFactories.GetFactory(ProviderElement.Provider).CreateParameter();//Config.Instance.ProviderFactory.CreateParameter();
            parameter.ParameterName = parameterName;//parameterName.Replace("@", "");
            parameter.Value = value;
            return parameter;
        }

        public TableRelationType GetTableRelationType(Type firstType, Type secondType)
        {
            var firstProperties = DynamicAssignment.Instance.GetDictionary(firstType);
            var secondProperties = DynamicAssignment.Instance.GetDictionary(secondType);

            var firstRelation = GetTableRelationType(secondType, firstProperties);
            var secondRelation = GetTableRelationType(firstType, secondProperties);

            var relation = (TableRelationType)Enum.Parse(typeof(TableRelationType), string.Format("{0}2{1}", firstRelation, secondRelation));
            if (firstType == secondType && relation == TableRelationType.Many2Many) return TableRelationType.One2Many;
            return relation;
        }

        private SingleTableRelationType GetTableRelationType(Type compareType, PropertyInvokerDic properties)
        {
            foreach (var property in properties)
            {
                var propertyType = property.Value.PropertyInfo.PropertyType;
                if (propertyType == compareType) return SingleTableRelationType.One;
                if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(ITable<>) && propertyType.GetGenericArguments()[0] == compareType)
                    return SingleTableRelationType.Many;
                //if(property.Value.PropertyInfo.PropertyType.IsGenericType && )
            }
            return SingleTableRelationType.Zero;
        }

        public bool CreateTable(Type type)
        {
            lock (_sync)
            {
                var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
                //var schemInfo = schem.GetSchem(type);
                //var mainProperties = DynamicAssignment.Instance.GetDictionary(type);
                //if (schem.ExtistTable(type.GetTableName()))
                //{
                //    var properties = DynamicAssignment.Instance.GetDictionary(type);
                //    var schemInfo = schem.GetSchem(type);
                //    return true;
                //}

                var createCache = new HashSet<Type>();

                //根据对象创建表
                var primaryKeyInfos = this.GetTableInfos(type);
                foreach (var item in primaryKeyInfos)
                {
                    //var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
                    var schemInfo = schem.GetSchem(item.Key);
                    var properties = DynamicAssignment.Instance.GetDictionary(item.Key);
                    //var mainProperties = DynamicAssignment.Instance.GetDictionary(item.Key);

                    var extistTable = schem.ExtistTable(item.Key.GetTableName());
                    //var columNameNum = schemInfo.Colunms.Count + schemInfo.ForeignKeys.Count;
                    //if (!schemInfo.Colunms.Contains(schemInfo.PrimaryKeyName)) columNameNum += 1;

                    var c_dic = schemInfo.Colunms.ToDictionary(x=>x);
                    var isColumChanged = false;
                    foreach (var property in properties)
                    {
                        var typeCode = Type.GetTypeCode(property.Value.PropertyInfo.PropertyType);
                        if (typeCode != TypeCode.Object 
                            || property.Value.PropertyInfo.PropertyType == typeof(Guid) 
                            || property.Value.PropertyInfo.PropertyType == typeof(byte[]))
                        {
                            var propertyName = property.Key.ToLower();
                            if (propertyName == schemInfo.PrimaryKeyName) continue;
                            if(!c_dic.ContainsKey(propertyName))
                            {
                                isColumChanged = true;
                                break;
                            }
                        }
                        else
                        {
                            var r_schem = schem.GetSchem(property.Value.PropertyInfo.PropertyType);
                            if(!schemInfo.ForeignKeys.ContainsKey(r_schem.TableName))
                            {
                                isColumChanged = true;
                                break;
                            }
                        }
                    }

                    if (extistTable == false || isColumChanged)
                    {
                        schem.RemoveSchem(item.Key);
                        createCache.Add(item.Key);
                        if (extistTable == false)
                        {
                            this.CreateTable(item.Key.GetTableName(), item.Value.PropertyName, this.GetDbType(item.Value.PropertyInfo.PropertyType));
                        }
                        foreach (var property in properties)
                        {
                            if (property.Value == item.Value || schemInfo.Colunms.Contains(property.Value.PropertyName.ToLower())) continue;
                            var attributeNum = property.Value.PropertyInfo.GetCustomAttributes(typeof(IgnoreColumnAttribute), true).Count();
                            if (attributeNum != 0) continue;
                            var typeCode = Type.GetTypeCode(property.Value.PropertyInfo.PropertyType);
                            if (typeCode != TypeCode.Object || property.Value.PropertyInfo.PropertyType == typeof(Guid) || property.Value.PropertyInfo.PropertyType == typeof(byte[]))
                            {
                                this.CreateBaseType(item.Key, property.Value.PropertyInfo);
                            }
                        }
                    }
                }

                //var relations = this.GetTableRelationType() 

                //关系创建
                var relations = this.GetRelationInfos(type);
                //var relation = relations.Where(x => x.PKType == type);
                foreach (var item in relations)
                {
                    if (!createCache.Contains(item.PKType)) continue;
                    var newschemInfo = schem.GetForeignKey(item.PKType.GetTableName()).ToDictionary(x => x.Table);
                    if (newschemInfo.ContainsKey(item.FKType.GetTableName())) continue;
                    //if (item.FKType != type && item.PKType != type) continue;

                    switch (item.TableRelationType)
                    {
                        case TableRelationType.Many2Zero:
                            this.CreateOneToOneRealtion(item.PKType, item.FKType);
                            break;
                        case TableRelationType.Many2One:
                            this.CreateOneToOneRealtion(item.PKType, item.FKType);
                            return true;
                            //return true;
                        case TableRelationType.Many2Many:
                            this.CreateManyToManyRealtion(item.PKType, item.FKType);
                            return true;
                        case TableRelationType.One2Many:
                            this.CreateOneToOneRealtion(item.FKType, item.PKType);
                            return true;
                        case TableRelationType.One2One:
                        case TableRelationType.One2Zero:
                            this.CreateOneToOneRealtion(item.FKType, item.PKType);
                            break;
                            //return true;
                        case TableRelationType.Zero2One:
                        case TableRelationType.Zero2Zero:
                        case TableRelationType.Zero2Many:
                        default:
                            break;
                    }

                    schem.RemoveSchem(item.PKType);
                }
                //foreach (var item in relations)
                //{
                //}
                return true;
            }
        }

        protected void CreateOneToOneRealtion(Type pkTable, Type fkTable)
        {
            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);

            var tableName = pkTable.GetTableName();
            var tablePrimary = schem.GetPrimaryKey(tableName);
            var tablePrimaryProperty = DynamicAssignment.Instance.GetDictionary(pkTable)[tablePrimary].PropertyInfo;

            var outterTableName = fkTable.GetTableName();
            var outterPrimary = schem.GetPrimaryKey(outterTableName);
            var outterPrimaryProperty = DynamicAssignment.Instance.GetDictionary(fkTable)[outterPrimary].PropertyInfo;

            var tableNameTemp = string.Format("{0}_{1}_Relation", tableName, outterTableName);

            this.CreateFK(outterTableName, string.Format("{0}Relation", tableName), this.GetDbType(tablePrimaryProperty.PropertyType), tableName, tablePrimary);
        }
        protected void CreateManyToManyRealtion(Type pkTable, Type fkTable)
        {
            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);

            var table1Name = pkTable.GetTableName();
            var table1PrimaryKey = schem.GetPrimaryKey(table1Name);
            var table1PrimaryKeyProperty = DynamicAssignment.Instance.GetDictionary(pkTable)[table1PrimaryKey].PropertyInfo;

            var table2Name = fkTable.GetTableName();
            var table2PrimaryKey = schem.GetPrimaryKey(table2Name);
            var table2PrimaryKeyProperty = DynamicAssignment.Instance.GetDictionary(fkTable)[table2PrimaryKey].PropertyInfo;

            //创建中间关联表
            var relationTableName = string.Format("{0}_{1}_Relation", table1Name, table2Name);
            this.CreateTable(relationTableName, "id", "int", true);

            this.CreateFK(relationTableName, string.Format("{0}id", table1Name), this.GetDbType(table1PrimaryKeyProperty.PropertyType), table1Name, table1PrimaryKey);
            this.CreateFK(relationTableName, string.Format("{0}id", table2Name), this.GetDbType(table2PrimaryKeyProperty.PropertyType), table2Name, table2PrimaryKey);
        }

        protected Dictionary<Type, IPropertyInvoker> GetTableInfos(Type type)
        {
            //全部的需要生成的表格
            var tables = new Dictionary<Type, IPropertyInvoker>();

            Stack<Type> stack = new Stack<Type>();
            stack.Push(type);
            HashSet<Type> hashSet = new HashSet<Type>();

            while (stack.Count > 0)
            {
                var typeTemp = stack.Pop();//第一个类型
                if (hashSet.Contains(typeTemp)) continue;
                hashSet.Add(typeTemp);
                var properties = DynamicAssignment.Instance.GetDictionary(typeTemp);
                //Primary Key
                var primarykey = properties.FirstOrDefault(p => p.Key.ToLower().Equals("id"));
                tables.Add(typeTemp, primarykey.Value);


                foreach (var property in properties)
                {
                    var propertyType = property.Value.PropertyInfo.PropertyType;
                    var typeCode = Type.GetTypeCode(propertyType);
                    if (typeCode == TypeCode.Object && propertyType != typeof(Guid))
                    {
                        if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(ITable<>))
                        {
                            var temp = propertyType.GetGenericArguments()[0];
                            stack.Push(temp);
                        }
                        else if (property.Value.PropertyInfo.PropertyType == typeof(byte[]) || property.Value.PropertyInfo.PropertyType == typeof(Guid))
                        {

                        }
                        else
                            stack.Push(propertyType);
                    }
                }

            }
            return tables;
        }

        protected IEnumerable<RelationInfo> GetRelationInfos(Type type)
        {
            var cache = new List<RelationInfo>();
            var hashSet = new HashSet<Type>();
            var stack = new Stack<Type>();
            stack.Push(type);
            while (stack.Count > 0)
            {
                var typeTemp = stack.Pop();
                if (hashSet.Contains(typeTemp)) continue;
                hashSet.Add(typeTemp);
                var properties = DynamicAssignment.Instance.GetDictionary(typeTemp);
                foreach (var property in properties)
                {
                    var outterType = property.Value.PropertyInfo.PropertyType;
                    var typeCode = Type.GetTypeCode(outterType);
                    if (typeCode == TypeCode.Object && outterType != typeof(Guid) && outterType != typeof(byte[]))
                    {
                        if (outterType.IsGenericType && outterType.GetGenericTypeDefinition() == typeof(ITable<>))
                        {
                            outterType = outterType.GetGenericArguments()[0];
                            stack.Push(outterType);
                        }
                        else
                        {
                            stack.Push(outterType);
                        }
                        var relationType = this.GetTableRelationType(typeTemp, outterType);
                        var info = new RelationInfo() { PKType = typeTemp, FKType = outterType };
                        info.TableRelationType = relationType;
                        cache.Add(info);
                        if (info.TableRelationType == TableRelationType.Many2Many) hashSet.Add(outterType);
                        //if (outterType.IsGenericType && outterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                        //{
                        //outterType = outterType.GetGenericArguments()[0];
                        //stack.Push(outterType);
                        //var relation = DynamicAssignment.Instance.GetDictionary(outterType).FirstOrDefault(x => x.Value.PropertyInfo.PropertyType.IsGenericType
                        //    && x.Value.PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
                        //    && x.Value.PropertyInfo.PropertyType.GetGenericArguments()[0] == typeTemp);//查询IEnumerable
                        //var info = new RelationInfo() { PKType = typeTemp, FKType = outterType };
                        //info.RelationType = relation.Value == null ? RelationType.One2Many : RelationType.Many2Many;
                        //cache.Add(info);
                        //if (info.RelationType == RelationType.Many2Many) hashSet.Add(outterType);

                        //}
                        //else
                        //{
                        //    stack.Push(outterType);
                        //    cache.Add(new RelationInfo()
                        //    {
                        //        PKType = typeTemp,
                        //        FKType = outterType,
                        //        RelationType = RelationType.One2One
                        //    });
                        //}
                    }
                }
            }
            return cache;
        }

        protected abstract string GetDbType(Type type);
    }

    class EntityRelation
    {
        public Type FirstType { get; set; }
        public PropertyInfo PrimaryKey { get; set; }
    }
}
